﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Security.Cryptography;
using System.IO;
using System.Data;

namespace ClientLib
{
    public class ClientUtil
    {
        double _offsetmils = 0;
        string _sessionid = "";
        string _serviceurl = "";

        string _publickey = "";
        string _symmkey = "";
        string _encsymmkey = "";

        string _requestid = "";
        string _requesttime = "";
        string _domainname = "";
        string _username = "";
        string _password = "";

        string _methodname = "";
        string _requestmessage = "";

        string _userid = "";
        string _secgrplist = "";

        string _responsecode = "";
        string _responsetype = "";
        string _methodresult = "";

        string _starttime = "";
        string _endtime = "";
        string _responsetime = "";

        DataTable _groupkeys = new DataTable();

        public ClientUtil()
        {
            
        }

        /// <summary>
        /// Milliseconds difference between client system clock and server system clock
        /// (assumes that all servers in a stateless server farm are synched to a timeserver)
        /// </summary>
        public double Offset
        {
            set { _offsetmils = value; }
            get { return _offsetmils; }
        }

        /// <summary>
        /// Common GUID value used to link all metrics data for a user session together in the database
        /// </summary>
        public string SessionID
        {
            set { _sessionid = value; }
            get { return _sessionid; }
        }

        /// <summary>
        /// URL selected during login
        /// </summary>
        public string ServiceURL
        {
            set { _serviceurl = value; }
            get { return _serviceurl; }
        }

        /// <summary>
        /// RSA public key of the particular web application hosting the web services
        /// (different key pairs per web application on the web server host)
        /// </summary>
        public string PublicKey
        {
            set { _publickey = value; }
            get { return _publickey; }
        }

        /// <summary>
        /// cleartext username
        /// </summary>
        public string UserName
        {
            set { _username = value; }
            get { return _username; }
        }

        /// <summary>
        /// cleartext password
        /// </summary>
        public string Password
        {
            set { _password = value; }
            get { return _password; }
        }

        /// <summary>
        /// unique userid GUID returned from the server userlogin method
        /// </summary>
        public string UserID
        {
            set { _userid = value; }
            get { return _userid; }
        }

        /// <summary>
        /// cleartext AES 256 symmetric key (reset per method call)
        /// </summary>
        public string SymmKey
        {
            get { return _symmkey; }
        }

        /// <summary>
        /// AES 256 symmetric key encrypted with service RSA public key
        /// </summary>
        public string EncSymmKey
        {
            get { return _encsymmkey; }
        }

        /// <summary>
        /// unique GUID per method call, used to map requests to responses in asynchronous environments
        /// </summary>
        public string RequestID
        {
            set { _requestid = value; }
            get { return _requestid; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string RequestMessage
        {
            set { _requestmessage = value; }
            get { return _requestmessage; }
        }

        /// <summary>
        /// Success, Empty, AppError, Exception
        /// (apperror deprecated)
        /// </summary>
        public string ResponseCode
        {
            set { _responsecode = value; }
            get { return _responsecode; }
        }

        /// <summary>
        /// datatype of method response:  Int, Number, Text, XML, JSON, DataTable
        /// (dates generally represented as text values, converted to datetime of specific platforms at endpoints)
        /// </summary>
        public string Type
        {
            set { _responsetype = value; }
            get { return _responsetype; }
        }

        /// <summary>
        /// value returned by the internal method called
        /// </summary>
        public string Result
        {
            set { _methodresult = value; }
            get { return _methodresult; }
        }

        /// <summary>
        /// DataTable of group names, ID's, and encryption keys
        /// </summary>
        public DataTable GroupKeys
        {
            set { _groupkeys = value; }
            get { return _groupkeys; }
        }

        public string RequestTime
        {
            get { return _requesttime; }
        }

        public string StartTime
        {
            get { return _starttime; }
        }

        public string EndTime
        {
            get { return _endtime; }
        }

        public string ResponeTime
        {
            get { return _responsetime; }
        }

        /// <summary>
        /// scans groupkey datatable for groupname, returns symmetric key
        /// </summary>
        /// <param name="groupName"></param>
        /// <returns></returns>
        public string GetGroupKeyByName(string groupName)
        {
            string symmKey = "";

            if (_groupkeys.Rows.Count > 0)
            {
                foreach (DataRow dr in _groupkeys.Rows)
                {
                    if (dr["GroupName"].ToString() == groupName) symmKey = dr["GroupKey"].ToString();
                }
            }

            return symmKey;
        }

        /// <summary>
        /// scans groupkey datatable for groupid, returns symmetric key
        /// </summary>
        public string GetGroupKeyByID(string groupID)
        {
            string symmKey = "";

            if (_groupkeys.Rows.Count > 0)
            {
                foreach (DataRow dr in _groupkeys.Rows)
                {
                    if (dr["GroupID"].ToString() == groupID) symmKey = dr["GroupKey"].ToString();
                }
            }

            return symmKey;
        }

        /// <summary>
        /// Resets the requestid value for each new API call
        /// </summary>
        /// <param name="clientOffset"></param>
        public void ResetRequestID()
        {
            _requestid = System.Guid.NewGuid().ToString();
        }

        /// <summary>
        /// Calculates the number of milliseconds since Jan 1, 1970 in place of a datetime or timestamp
        /// (easier to use across languages, platforms and devices)
        /// </summary>
        public string GetTimeInMilliseconds()
        {
            string timeMils = "";

            DateTime baseTime = new DateTime(1970, 1, 1, 0, 0, 0);
            DateTime currentTime = DateTime.UtcNow;
            TimeSpan timemillisec = currentTime.Subtract(baseTime);
            timeMils = timemillisec.TotalMilliseconds.ToString();

            return timeMils;
        }

        /// <summary>
        /// Calculates the server time that corresponds to the current client system clock by applying an offset (used by the client only).
        /// This correction process handles the fact that client machine system clock will not be in synch with any of the servers.
        /// </summary>
        /// <returns></returns>
        public void SetAdjustedTime()
        {
            double adjustedtime = 0;
            string timemillisec = GetTimeInMilliseconds();
            double currenttime = Convert.ToDouble(timemillisec); ;
            adjustedtime = currenttime - _offsetmils;
            _requesttime = adjustedtime.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        public string GetNewID()
        {
            return System.Guid.NewGuid().ToString().Replace("-", "");
        }

        /// <summary>
        /// 
        /// </summary>
        public string PasswordHash(string username, string password)
        {
            string pwordhash = "";

            // create hash value of username+password
            string concatStr = username + password;
            HashAlgorithm hash = new SHA256Managed();
            byte[] clearArray = System.Text.ASCIIEncoding.ASCII.GetBytes(concatStr);
            byte[] hashArray = hash.ComputeHash(clearArray);
            string hashStr = Convert.ToBase64String(hashArray);
            pwordhash = hashStr;

            return pwordhash;
        }

        // ****************************************
        // ****************************************

        // Input XML Format
        // ----------------
        //<RequestMessage>
        //    <SessionGUID />   -- unique identifier per user session
        //    <RequestGUID />   -- unique identifier per request message
        //    <RequestTime />   -- milliseconds since Jan 1 1970
        //    <UserName />      -- cleartext username
        //    <Password />      -- cleartext password (requires message to be encrypted during transport)
        //    <MethodName />    -- apiname.methodname.versionname (fully qualified method name)
        //    <VersionParams>   -- collection of parameter name+value pairs enclosed in CDATA block
        //        <![CDATA[ param1name^param1value|param2name^param2value ]]>
        //    </VersionParams>
        //</RequestMessage>
        // -----------------
        // * VersionParams cannot contain ^, |, or characters that conflict with CDATA block

        // ****************************************
        // ****************************************

        public string BuildMessageXML(string MethodName, Dictionary<string, string> methodParams, bool encrypted)
        {
            string encryptedMessage = "";

            // update request id and request time
            ResetRequestID();
            SetAdjustedTime();

            // create request message as xml fragment
            string xmlStr = "<RequestMessage>" +
                            "<SessionGUID>" + _sessionid + "</SessionGUID>" +
                            "<RequestGUID>" + _requestid + "</RequestGUID>" +
                            "<RequestTime>" + _requesttime + "</RequestTime>" +
                            "<UserName>" + _username + "</UserName>" +
                            "<Password>" + _password + "</Password>" +
                            "<MethodName>" + MethodName + "</MethodName><VersionParams><![CDATA[";

            string delimiter = "";
            foreach (KeyValuePair<string, string> kvp in methodParams)
            {
                xmlStr += delimiter + kvp.Key + "^" + kvp.Value;
                delimiter = "|";
            }

            xmlStr += "]]></VersionParams></RequestMessage>";
            _requestmessage = xmlStr;

            if (encrypted)
            {
                // create new symmetric key, encrypt message
                EncryptUtil encutil = new EncryptUtil();
                _symmkey = encutil.CreateSymmKey();
                encryptedMessage = encutil.EncryptString2(xmlStr, _symmkey);

                // encrypt symmetric key with public RSA key
                _encsymmkey = encutil.RSAEncryptKey(_symmkey, _publickey);
            }
            else
            {
                encryptedMessage = xmlStr;
                _symmkey = "";
                _encsymmkey = "";
            }

            return encryptedMessage;
        }

        // ****************************************
        // ****************************************

        // Output XML Format
        // -----------------
        //<ResponseMessage>
        //    <RequestGUID />   -- unique identifier per request message
        //    <RequestTime />   -- milliseconds since Jan 1 1970
        //    <UserName />      -- cleartext username
        //    <MethodName />    -- apiname.methodname.versionname (fully qualified method name)
        //    <StartTime />     -- milliseconds time when API processing started
        //    <EndTime />       -- milliseconds time when API processing ended
        //    <ResponseCode />  -- Success, Empty, Error, Exception
        //    <ResponseType />  -- Int, Number, Text, XML, JSON, DataTable
        //    <MethodResult>          -- internal method results enclosed in CDATA block
        //        <![CDATA[ internal method results ]]>
        //    </MethodResult>
        //</ResponseMessage>
        // -----------------
        // * Result cannot contain characters that conflict with CDATA block

        // ****************************************
        // ****************************************

        /// <summary>
        /// 
        /// </summary>
        /// <param name="resultXML"></param>
        public void ParseResponseMessage(string resultXML)
        {

            // add xml header to result fragment, then load the XML message into an XML document object
            XmlDocument xmlDoc = new XmlDocument();
            string xmlResStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + resultXML;
            xmlDoc.LoadXml(xmlResStr);

            // use XPATH to extract the relevant elements
            _requestid = xmlDoc.SelectSingleNode("ResponseMessage/RequestGUID").InnerText;
            _responsecode = xmlDoc.SelectSingleNode("ResponseMessage/ResponseCode").InnerText;
            _responsetype = xmlDoc.SelectSingleNode("ResponseMessage/ResponseType").InnerText;
            _starttime = xmlDoc.SelectSingleNode("ResponseMessage/StartTime").InnerText;
            _endtime = xmlDoc.SelectSingleNode("ResponseMessage/EndTime").InnerText;
            _responsetime = GetTimeInMilliseconds();

            XmlNode resultNode = xmlDoc.SelectSingleNode("ResponseMessage/MethodResult");

            if (_responsetype == "XML" || _responsetype == "DataTable")
            {
                _methodresult = resultNode.InnerXml;
            }
            else
            {
                _methodresult = resultNode.InnerText;
            }
        }

    }
}
